#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "driver/i2s.h"

#define I2S_BCLK      5
#define I2S_WSR       6
#define I2S_MIC_DATA  7
#define I2S_PORT      I2S_NUM_0

#define SAMPLE_RATE   16000
#define DMA_BUF_LEN   128

const char* ssid = "xxx";
const char* password = "xxx";
const char* azure_endpoint = "https://xxx.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=zh-CN";
const char* azure_key = "xxx";

// 10秒音频缓冲区
#define MAX_RECORD_SEC 10
#define MAX_AUDIO_SIZE (SAMPLE_RATE * 2 * MAX_RECORD_SEC) // 16bit单声道

uint8_t* audio_buffer = NULL;
size_t audio_buffer_pos = 0;

// 声音检测参数
#define VOICE_START_THRESHOLD 100   // 只有声音大于此阈值才开始录音
#define SILENCE_THRESHOLD     500    // 录音过程中小于此阈值认为静音
#define SILENCE_DURATION_MS   500    // 静音持续时间

enum State {
  WAIT_FOR_VOICE,
  RECORDING
};

State state = WAIT_FOR_VOICE;
unsigned long last_voice_time = 0;
void setup() {
  Serial.begin(115200);
  setupWiFi();
  setupI2SMic();

  audio_buffer = (uint8_t*)ps_malloc(MAX_AUDIO_SIZE);
  if (!audio_buffer) {
    Serial.println("PSRAM malloc failed!");
    while (1) delay(1000);
  }
  audio_buffer_pos = 0;
}
void loop() {
  int16_t samples[DMA_BUF_LEN];
  size_t bytes_read = 0;
  i2s_read(I2S_PORT, (void*)samples, DMA_BUF_LEN * sizeof(int16_t), &bytes_read, portMAX_DELAY);

  bool has_voice = false;
  for (int i = 0; i < DMA_BUF_LEN; i++) {
    if (abs(samples[i]) > VOICE_START_THRESHOLD) {
      has_voice = true;
      break;
    }
  }

  switch (state) {
    case WAIT_FOR_VOICE:
      if (has_voice) {
        Serial.println("检测到声音，开始录音...");
        audio_buffer_pos = 0;
        last_voice_time = millis();
        state = RECORDING;
        // 保存第一帧
        memcpy(audio_buffer, samples, bytes_read);
        audio_buffer_pos += bytes_read;
      }
      break;

    case RECORDING:
      // 检查是否有声音
      bool frame_has_voice = false;
      for (int i = 0; i < DMA_BUF_LEN; i++) {
        if (abs(samples[i]) > SILENCE_THRESHOLD) {
          frame_has_voice = true;
          break;
        }
      }

      // 保存音频
      if (audio_buffer_pos + bytes_read < MAX_AUDIO_SIZE) {
        memcpy(audio_buffer + audio_buffer_pos, samples, bytes_read);
        audio_buffer_pos += bytes_read;
      } else {
        Serial.println("录音缓冲区满，自动结束录音");
        frame_has_voice = false; // 强制结束
      }

      if (frame_has_voice) {
        last_voice_time = millis();
      }

      // 检测静音
      if ((millis() - last_voice_time) > SILENCE_DURATION_MS) {
        Serial.println("检测到静音，结束录音并识别...");
        String result = sendToAzure(audio_buffer, audio_buffer_pos);
        audio_buffer_pos = 0;
        state = WAIT_FOR_VOICE;
        Serial.println("等待下一轮声音...");
      }
      break;
  }
}
String sendToAzure(uint8_t* audio_data, size_t data_len) {
  HTTPClient http;
  http.begin(azure_endpoint);
  http.addHeader("Ocp-Apim-Subscription-Key", azure_key);
  http.addHeader("Content-Type", "audio/wav; codecs=audio/pcm; samplerate=16000");

  // 构造 WAV 头
  uint8_t wav_header[44];
  createWavHeader(wav_header, data_len, SAMPLE_RATE);
  
  uint8_t* wav_data = (uint8_t*)ps_malloc(data_len + 44);
  if (!wav_data) {
    Serial.println("WAV buffer ps_malloc failed!");
    return "";
  }
  memcpy(wav_data, wav_header, 44);
  memcpy(wav_data + 44, audio_data, data_len);

  int httpResponseCode = http.POST(wav_data, data_len + 44);
  String result;
  if (httpResponseCode > 0) {
    String response = http.getString();
    Serial.println("Azure Response:");
    Serial.println(response);

    DynamicJsonDocument doc(1024);
    deserializeJson(doc, response);
    const char* text = doc["DisplayText"];
    if (text) {
      Serial.print("识别结果: ");
      Serial.println(text);
      result = text;
    } else {
      Serial.println("未识别到文本");
    }
  } else {
    Serial.printf("HTTP Error: %d\n", httpResponseCode);
  }

  free(wav_data);
  http.end();
  return result;
}
void createWavHeader(uint8_t* header, uint32_t dataSize, uint32_t sampleRate) {
  uint32_t fileSize = dataSize + 36;
  uint16_t audioFormat = 1;
  uint16_t numChannels = 1;
  uint16_t bitsPerSample = 16;
  uint32_t byteRate = sampleRate * numChannels * bitsPerSample / 8;
  uint16_t blockAlign = numChannels * bitsPerSample / 8;

  memcpy(header, "RIFF", 4);
  *(uint32_t*)(header + 4) = fileSize;
  memcpy(header + 8, "WAVEfmt ", 8);
  *(uint32_t*)(header + 16) = 16;
  *(uint16_t*)(header + 20) = audioFormat;
  *(uint16_t*)(header + 22) = numChannels;
  *(uint32_t*)(header + 24) = sampleRate;
  *(uint32_t*)(header + 28) = byteRate;
  *(uint16_t*)(header + 32) = blockAlign;
  *(uint16_t*)(header + 34) = bitsPerSample;
  memcpy(header + 36, "data", 4);
  *(uint32_t*)(header + 40) = dataSize;
}
void setupWiFi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
}

void setupI2SMic() {
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 4,
    .dma_buf_len = DMA_BUF_LEN,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };

  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_BCLK,
    .ws_io_num = I2S_WSR,
    .data_out_num = -1,
    .data_in_num = I2S_MIC_DATA
  };

  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
}
